#include "engine.h"
#include "particlesystem.h"
#include "render/rendersystem.h"
#include <gl/glew.h>
#include "math/random.h"

DEFINE_ENTITY(particlesystem_t,entity_t);
PROPERTY_BINDING_BEGIN(particlesystem_t)
BIND_PROPERTY_DATA(particlesystem_t,texture_name,stringhash,&texture_property_data);
BIND_PROPERTY(particlesystem_t,particlenum_max,int);
BIND_PROPERTY(particlesystem_t,spawn_freq,float);
BIND_PROPERTY(particlesystem_t,emitter_lifespan,int);
BIND_PROPERTY(particlesystem_t,emitterbox_size,float3);

BIND_PROPERTY(particlesystem_t,particle_size_min,float);
BIND_PROPERTY(particlesystem_t,particle_size_max,float);
BIND_PROPERTY(particlesystem_t,particle_size_velocity,float);
BIND_PROPERTY(particlesystem_t,particle_size_damping,float);

BIND_PROPERTY(particlesystem_t,rotation_default_min,angle);
BIND_PROPERTY(particlesystem_t,rotation_default_max,angle);

BIND_PROPERTY(particlesystem_t,rotation_speed_min,angle);
BIND_PROPERTY(particlesystem_t,rotation_speed_max,angle);
BIND_PROPERTY(particlesystem_t,rotation_speed_damping,float);

BIND_PROPERTY(particlesystem_t,velocity_min,float);
BIND_PROPERTY(particlesystem_t,velocity_max,float);
BIND_PROPERTY(particlesystem_t,velocity_damping,float);

BIND_PROPERTY(particlesystem_t,gravity,float);

BIND_PROPERTY(particlesystem_t,spawn_angle,angle);

BIND_PROPERTY(particlesystem_t,global_color,color4f);
BIND_PROPERTY(particlesystem_t,color_random_min,color4f);
BIND_PROPERTY(particlesystem_t,color_random_max,color4f);
BIND_PROPERTY(particlesystem_t,fadein_time,float);
BIND_PROPERTY(particlesystem_t,fadeout_time,float);
BIND_PROPERTY(particlesystem_t,maradjonugy_time,float);
BIND_PROPERTY(particlesystem_t,material,stringhash);
BIND_PROPERTY(particlesystem_t,autostart,bool);

PROPERTY_BINDING_END




particlesystem_t::particlesystem_t():ro(this)
{
	autostart=true;
	material="particle";
	particlenum_max=100;
	texture_name="textures/white";

	emitter_lifespan=-1;
	emitterbox_size.set(0,0,0);

	spawn_freq=5;

	particle_size_min=5;
	particle_size_max=10;
	particle_size_velocity=0;
	particle_size_damping=1;

	rotation_default_min=0;
	rotation_default_max=0;

	rotation_speed_min=-DEG2RAD*10;
	rotation_speed_max=DEG2RAD*10;
	rotation_speed_damping=1;

	velocity_min=100;
	velocity_max=100;
	velocity_damping=1;

	spawn_angle=DEG2RAD*90;

	global_color.set(1,1,1,1);
	color_random_min.set(1,1,1,1);
	color_random_max.set(1,1,1,1);

	fadein_time=1;
	fadeout_time=1;
	maradjonugy_time=1;

	gravity=0;


}

void particlesystem_t::onload()
{
	entity_t::onload();
	mat=RSYS->get_material(material);
}

void particlesystem_t::init()
{
	entity_t::init();
//	vb.init(particlenum_max*4,rendersystem_t::get_vdecl("particle"));
	GLASSERT;
	vb.init_dynamic(mat->vdecl);
	GLASSERT;
	tex=rendersystem_t::get_texture(texture_name);
	GLASSERT;
//	sh=rendersystem_t::get_shader("particle");
	GLASSERT;

	particlebuf.clear();

	spawn_frac=1;
	lifetime_counter=emitter_lifespan;
	started=false;
	if (autostart)
		start();
	GLASSERT;
}

void particlesystem_t::spawn()
{
	float4x4 mtx=getworldmtx();

	float tilecount=1;
	float start_scale=length(mtx.axis(0));
	float3 start_direction_norm(mtx.axis(2)); normalize(start_direction_norm);
	int sml=fabsf(start_direction_norm.x)<fabsf(start_direction_norm.y) ? 0 : 1;
	sml=fabsf(start_direction_norm[sml])<fabsf(start_direction_norm.z) ? sml : 2;
	float3 ortho_direction;
	ortho_direction[sml]=0;
	ortho_direction[(sml+1)%3]=start_direction_norm[(sml+2)%3];
	ortho_direction[(sml+2)%3]=-start_direction_norm[(sml+1)%3];
	normalize(ortho_direction);

	float ha=spawn_angle/2;

	float3 emitmin=-emitterbox_size/2;
	float3 emitmax=emitterbox_size/2;


	spawn_frac+=spawn_freq;
	int spawn_int=(int)spawn_frac;
	spawn_frac-=(float)spawn_int;
	//add_new_particles
	for (int n=0; n<spawn_int; ++n)
	{
		float barmi=sfrand(0.0f,1.0f);
		barmi=sqrtf(barmi);
		barmi*=srand() & 1 ? ha : -ha;

		particlenode_t p;
		p.pos.x=sfrand(emitmin.x,emitmax.x);
		p.pos.y=sfrand(emitmin.y,emitmax.y);
		p.pos.z=sfrand(emitmin.z,emitmax.z);
		p.pos=mtx.translate()+p.pos.x*mtx.axis(0)+p.pos.y*mtx.axis(1)+p.pos.z*mtx.axis(2);
		p.vel=rotate(mtx.axis(2),ortho_direction,barmi);//sfrand(-ha,ha));
		p.vel=rotate(p.vel,start_direction_norm,sfrand(-PI,PI));
		p.vel*=sfrand(velocity_min,velocity_max);
		p.rot=sfrand(rotation_default_min, rotation_default_max);
		p.rotvel=sfrand(rotation_speed_min,rotation_speed_max);

		p.size=sfrand(particle_size_min,particle_size_max)*start_scale;
		p.sizevel=particle_size_velocity;

		p.time=(fadein_time+fadeout_time+maradjonugy_time);
		//		p.attractor=attractor;
		p.color.set(sfrand(color_random_min.x, color_random_max.x),
			sfrand(color_random_min.y, color_random_max.y),
			sfrand(color_random_min.z, color_random_max.z),
			sfrand(color_random_min.w, color_random_max.w));

		particlebuf.push_back(p);

	}
}

void particlesystem_t::start()
{
	started=true;
	spawn_frac=1;
	lifetime_counter=emitter_lifespan;
}

void particlesystem_t::stop()
{
	started=false;
}

void particlesystem_t::update()
{
	entity_t::update();
	GLASSERT;
	if (started && lifetime_counter)
	{
		--lifetime_counter;
	}
	else
		started=false;

	if (started)
	{
		spawn();
	}

	GLASSERT;
	float dt=1/60.0f;

	for (uint32 n=0; n<particlebuf.size(); ++n)
	{
		particlenode_t& p=particlebuf[n];
		p.time-=dt;
		p.sizevel+=(particle_size_damping-1)*dt*p.sizevel;
		p.size+=dt*p.sizevel;

		if (p.time<0 || p.size<=0)
		{
			particlebuf[n--]=particlebuf.back();
			particlebuf.pop_back();
			continue;
		}

		p.vel.y-=dt*gravity;
		p.vel+=(velocity_damping-1)*dt*p.vel;

		p.rotvel+=(rotation_speed_damping-1)*dt*p.rotvel;
		p.pos+=dt*(p.vel);
		p.rot+=dt*p.rotvel;

	}

	GLASSERT;
}

void particlesystem_t::render()
{
	if (particlebuf.size())
		RSYS->render_custom_object(&ro);
}

void particlesystem_t::exit()
{
	entity_t::exit();
	particlebuf.clear();
}

void particlesystem_t::setupbuffers()
{
	if (!particlebuf.size())
		return;

	int tilecount=1;

	GLASSERT;
	particlevertex_t* bv=(particlevertex_t*)vb.lock(particlebuf.size()*4);
	GLASSERT;
	if (!bv) 
		return;

	for (uint32 n=0; n<particlebuf.size(); ++n)
	{
		const particlenode_t& p=particlebuf[n];


		float alpha;
		float t=((fadein_time+fadeout_time+maradjonugy_time))-p.time;
		if (t<fadein_time)
			alpha=t/(fadein_time);
		else if (t<(fadein_time+maradjonugy_time))
			alpha=1;
		else
			alpha=1-(t-(fadein_time+maradjonugy_time))/(fadeout_time);

		float4 particle_color=p.color;

		const float4 posrot(p.pos, p.rot);
		const float4 sizealpha(p.size, p.size, alpha, 0);

		bv->posrot=posrot;
		bv->sizealpha=sizealpha;
		bv->uv[0]=0;
		bv->uv[1]=1;
		bv->color=particle_color;
		++bv;

		bv->posrot=posrot;
		bv->sizealpha=sizealpha;
		bv->uv[0]=1;//32767;
		bv->uv[1]=1;
		bv->color=particle_color;
		++bv;

		bv->posrot=posrot;
		bv->sizealpha=sizealpha;
		bv->uv[0]=1;//32767;
		bv->uv[1]=0;//32767;
		bv->color=particle_color;
		++bv;

		bv->posrot=posrot;
		bv->sizealpha=sizealpha;
		bv->uv[0]=0;
		bv->uv[1]=0;//32767;
		bv->color=particle_color;
		++bv;
	}
	GLASSERT;
	vb.unlock();
	GLASSERT;

}

void particlesystem_t::onrender()
{
	if (!particlebuf.size())
		return;

	RSYS->global_shader_data.world_mtx.identity();
	GLASSERT;
	RSYS->set_object_data();
	GLASSERT;


	vb.set();
	RSYS->quadib.set();

	if (tex)
	{
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, tex->hwid);
	}
	mat->set();
//	sh->set();

	glDrawElements(GL_TRIANGLES,particlebuf.size()*6,GL_UNSIGNED_SHORT,0);

}

#ifdef LEO_DESIGNER
void particlesystem_t::onchangeproperty(const stringhash& propname)
{
	entity_t::onchangeproperty(propname);
	if (propname=="texture_name")
	{
		tex=rendersystem_t::get_texture(texture_name);
	}
	else if (propname=="material")
	{
		mat=RSYS->get_material(material);
	}
}
#endif



DEFINE_ENTITY(light_t,entity_t)
PROPERTY_BINDING_BEGIN(light_t)
	BIND_PROPERTY(light_t,radius,float);
	BIND_PROPERTY(light_t,color,color4f);
	BIND_PROPERTY(light_t,strength,float);
PROPERTY_BINDING_END

light_t::light_t()
{
	radius=10;
	color.set(1,1,1,1);
	strength=1;
}

void light_t::update()
{
	entity_t::update();
	RSYS->set_light(getworldmtx().translate(),radius,color*strength);
}

void get_orthogonal_axii(float3& o_axis1, float3& o_axis2, const float3& i_axissrc)
{
	if ((i_axissrc.z) > 0.707f || i_axissrc.z<-0.707f)
	{
		// y-z plane
		float  a = i_axissrc.y*i_axissrc.y + i_axissrc.z*i_axissrc.z;
		float k = 1/sqrt(a);
		o_axis1.x = 0;
		o_axis1.y = -i_axissrc.z*k;
		o_axis1.z = i_axissrc.y*k;

		o_axis2.x = a*k;
		o_axis2.y = -i_axissrc.x*o_axis1.z;
		o_axis2.z = i_axissrc.x*o_axis1.y;
	}
	else
	{
		// x-y plane
		float a = i_axissrc.x*i_axissrc.x + i_axissrc.y*i_axissrc.y;
		float k = 1/sqrtf(a);
		o_axis1.x = -i_axissrc.y*k;
		o_axis1.y = i_axissrc.x*k;
		o_axis1.z = 0;

		o_axis2.x = -i_axissrc.z*o_axis1.y;
		o_axis2.y = i_axissrc.z*o_axis1.x;
		o_axis2.z = a*k;
	}
}


void draw_circle(const float3& center, float radius, const float4& color)
{
	int segmentnum=20;
	float3 camx,camy;
	get_orthogonal_axii(camx,camy,RSYS->global_shader_data.camera_mtx.axis(2));

	float angle=(segmentnum-1)*2*PI/(segmentnum);
	float3 prevpos=center+radius*(cosf(angle)*camx+sinf(angle)*camy);
	for (int n=0; n<segmentnum; ++n)
	{
		angle=n*2*PI/segmentnum;
		float3 pos=center+radius*(cosf(angle)*camx+sinf(angle)*camy);

		RSYS->draw_line(prevpos,pos,color,color);
		prevpos=pos;
	}
}

#ifdef LEO_DESIGNER
void light_t::render()
{
	draw_circle(getworldmtx().translate(),radius,float4(1,1,1,1));
}
#endif

void start_particle(particlesystem_t* p)
{
	ASSERT(p && p->isa(particlesystem_t::get_class_typeid()));
	p->activate();
	p->start();
}
void stop_particle(particlesystem_t* p)
{
	ASSERT(p && p->isa(particlesystem_t::get_class_typeid()));
	p->stop();
}

#include "script/script.h"

script_fn_registering_begin(particle)
register_script_fn("void start_particlesystem(int)",start_particle);
register_script_fn("void stop_particlesystem(int)",stop_particle);
script_fn_registering_end
